package top.lixunda.commerce.common.core.controller.wrapper;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author LiXunda
 * @version time:2019/10/13 15:03
 * <p>
 * 存放Sql片段的块, 一种树形数据结构
 */
@SuppressWarnings("ALL")
@Slf4j
public class ConditionBlock implements IConditionBlock {

    public static final String OP_AND = "AND";

    public static final String OP_OR = "OR";

    /**
     * 块属性，and或者or
     */
    private String operation;

    /**
     * 父节点
     */
    private ConditionBlock parent;

    /**
     * 子块
     */
    private List<ConditionBlock> blocks;

    /**
     * 条件语句
     */
    private List<ConditionSegment> segs;

    public static ConditionBlock andSqlBlock() {
        return new ConditionBlock(OP_AND);
    }

    private static ConditionBlock orSqlBlock() {
        return new ConditionBlock(OP_OR);
    }

    public DataConditionBlock toDataConditionBlock() {
        DataConditionBlock dCon = new DataConditionBlock();
        dCon.setOp(this.getOperation());
        dCon.setSegs(this.getSegs());
        List<DataConditionBlock> list = new ArrayList<>();
        this.getBlocks().forEach(item -> list.add(item.toDataConditionBlock()));
        dCon.setBlocks(list);
        return dCon;
    }


    private ConditionBlock(String operation, ConditionBlock parentSqlBlock, List<ConditionBlock> blocks, List<ConditionSegment> segs) {
        this.setOperation(operation).setParent(parentSqlBlock);
        this.blocks = blocks == null ? new ArrayList<>() : blocks;
        this.segs = segs == null ? new ArrayList<>() : segs;
    }

    private ConditionBlock(String operation, ConditionBlock parentSqlBlock) {
        this(operation, parentSqlBlock, null, null);
    }

    private ConditionBlock(String operation) {
        this(operation, null);
    }

    private ConditionBlock() {
        this(OP_AND);
    }

    public String getOperation() {
        return this.operation;
    }

    public ConditionBlock setOperation(String op) {
        if (!(OP_AND.equals(op) || OP_OR.equals(op))) {
            throw new IllegalArgumentException("this sqlBlock operation must be OP_AND or OP_OR");
        }
        this.operation = op;
        return this;
    }

    public ConditionBlock setParent(ConditionBlock parentSqlBlock) {
        this.parent = parentSqlBlock;
        return this;
    }

    public ConditionBlock getParent() {
        return this.parent;
    }

    public List<ConditionBlock> getBlocks() {
        return this.blocks;
    }

    private ConditionBlock addSqlBlock(ConditionBlock sqlBlock) {
        if (sqlBlock == null) {
            log.warn("the sqlBlock is null, this is nothing to entity");
            return this;
        }
        this.blocks.add(sqlBlock);
        return this;
    }

    public List<ConditionSegment> getSegs() {
        return this.segs;
    }

    public ConditionBlock addSqlSegment(ConditionSegment conditionSegment) {
        if (conditionSegment == null) {
            log.warn("this conditionSegment is null, the action is nothing");
            return this;
        }
        this.segs.add(conditionSegment);
        return this;
    }

    @Override
    public ConditionBlock getRoot() {
        ConditionBlock conditionBlock = this;
        while (conditionBlock.parent != null) {
            conditionBlock = conditionBlock.parent;
        }
        return conditionBlock;
    }

    @Override
    public String toString() {
        if (this.segs.size() == 0 && this.blocks.size() == 0) {
            log.info("可以优化");
            return "";
        }
        StringBuilder builder = new StringBuilder();
        boolean flag;
        flag = !(this.parent == null || this.getOperation().equals(OP_AND) && this.parent.getOperation().equals(OP_OR));
        if (flag) {
            builder.append("(");
        }
        this.segs.forEach(item -> {
            builder.append(item.getValues().get(0)).append(" ").append(item.getOp());
            item.getValues().subList(1, item.getValues().size()).forEach(i -> builder.append(" ").append(i));
            builder.append(" ").append(this.operation).append(" ");
        });
        this.blocks.forEach(item -> {
            builder.append(item.getSql()).append(" ").append(this.operation).append(" ");
        });
        builder.delete(builder.length() - this.operation.length() - 1, builder.length());
        flag = !(this.parent == null || this.getOperation().equals(OP_AND) && this.parent.getOperation().equals(OP_OR));
        if (flag) {
            builder.append(")");
        }
        return builder.toString().trim();
    }

    @Override
    public String getSql() {
        return toString();
    }

    @Override
    public String getRootSql() {
        return getRoot().getSql();
    }

    public static ConditionBlock getRoot(ConditionBlock block) {
        return block == null ? block : block.getRoot();
    }

    @Override
    public ConditionBlock allEq(Map<String, String> map) {
        return this.and().allEqNormal(map);
    }

    @Override
    public ConditionBlock eq(String column, String value) {
        return this.and().eqNormal(column, value);
    }

    @Override
    public ConditionBlock ne(String column, String value) {
        return this.and().neNormal(column, value);
    }

    @Override
    public ConditionBlock gt(String column, String value) {
        return this.and().gtNormal(column, value);
    }

    @Override
    public ConditionBlock ge(String column, String value) {
        return this.and().geNormal(column, value);
    }

    @Override
    public ConditionBlock lt(String column, String value) {
        return this.and().ltNormal(column, value);
    }

    @Override
    public ConditionBlock le(String column, String value) {
        return this.and().leNormal(column, value);
    }

    @Override
    public ConditionBlock between(String column, String value1, String value2) {
        return this.and().betweenNormal(column, value1, value2);
    }

    @Override
    public ConditionBlock notBetween(String column, String value1, String value2) {
        return this.and().notBetweenNormal(column, value1, value2);
    }

    @Override
    public ConditionBlock like(String column, String value) {
        return this.and().likeNormal(column, value);
    }

    @Override
    public ConditionBlock notLike(String column, String value) {
        return this.and().notLikeNormal(column, value);
    }

    @Override
    public ConditionBlock likeLeft(String column, String value) {
        return this.and().likeNormal(column, value == null ? null : "%" + value);
    }

    @Override
    public ConditionBlock likeRight(String column, String value) {
        return this.and().likeNormal(column, value == null ? null : value + "%");
    }

    @Override
    public ConditionBlock isNull(String... column) {
        return this.and().isNullNormal(column);
    }

    @Override
    public ConditionBlock isNotNull(String... column) {
        return this.and().isNotNullNormal(column);
    }

    @Override
    public ConditionBlock in(String column, List<String> values) {
        return this.and().inNormal(column, values);
    }

    @Override
    public ConditionBlock notIn(String column, List<String> values) {
        return this.and().notInNormal(column, values);
    }

    @Override
    public ConditionBlock apply(String column, List<String> values) {
        return this.and().applyNormal(column,
                // ConditionSegment要求参数不能为空
                values == null || values.size() == 0 ? Collections.singletonList("") : values);
    }

    @Override
    public ConditionBlock or() {
        if (this.operation.equals(OP_OR)) {
            return this;
        } else {
            //当前为 OP_AND，新建一个and节点，返回的是一个and节点
            //新建一个or类型 的父节点
            ConditionBlock newConditionBlock = ConditionBlock.andSqlBlock();
            if (this.parent != null && this.parent.getOperation().equals(OP_OR)) {
                this.parent.addSqlBlock(newConditionBlock);
                return newConditionBlock.setParent(this.parent);
            }

            ConditionBlock newParent = ConditionBlock.orSqlBlock();
            newParent.addSqlBlock(newConditionBlock);
            newConditionBlock.setParent(newParent);
            if (this.parent != null) {
                this.parent.getBlocks().remove(this);
                this.parent.getBlocks().add(newParent);
            }
            newParent.setParent(this.parent);
            this.setParent(newParent);
            newParent.addSqlBlock(this);
            return newConditionBlock;
        }
    }

    @Override
    public ConditionBlock and() {
        if (this.operation.equals(OP_AND)) {
            return this;
        } else {
            //当前为 OP_OR, 创建一个and类型的子节点
            ConditionBlock andCondition = ConditionBlock.andSqlBlock().setParent(this);
            this.addSqlBlock(andCondition);
            return andCondition;
        }
    }

    @Override
    public ConditionBlock or(ConditionBlock sqlBlock) {
        if (sqlBlock == null) {
            log.warn("the sqlBlock not be null, so this method nothing to entity");
            return this;
        }

        ConditionBlock conditionBlock = this.or();
        //表示当前节点就是or节点，所以or（）方法才返回了本身的or节点，那么直接添加
        if (conditionBlock.getOperation().equals(OP_OR)) {
            conditionBlock.addSqlBlock(sqlBlock.getRoot().setParent(conditionBlock));
            //返回or节点
            return this;
        } else {
            //本身不是or节点，当前or()方法重新生成了一个了一个,替换了就好
            conditionBlock.parent.getBlocks().remove(conditionBlock);
            conditionBlock.parent.addSqlBlock(sqlBlock.getRoot().setParent(conditionBlock.parent));
            //返回or节点
            return conditionBlock.parent;
        }
    }

    @Override
    public ConditionBlock and(ConditionBlock sqlBlock) {
        if (sqlBlock == null) {
            log.warn("the sqlBlock not be null, so this method nothing to entity");
            return this;
        }

        if (OP_AND.equals(this.operation)) {
            //当前为and节点，直接添加为子节点
            return sqlBlock.getOperation().equals(OP_AND) ? merge(sqlBlock) : this.addSqlBlock(sqlBlock.getRoot().setParent(this));
        } else {
            //当前为or节点，新建一个and类型的子节点
            ConditionBlock block = ConditionBlock.andSqlBlock().setParent(this);
            this.addSqlBlock(block);
            return block.and(sqlBlock);
        }
    }

    /**
     * 将block合并到当前，只有都为and类型节点时才可以合并
     *
     * @param block
     */
    public ConditionBlock merge(ConditionBlock block) {
        block = block.getRoot();
        block.getSegs().forEach(item -> this.addSqlSegment(item));
        block.getBlocks().forEach(item -> this.addSqlBlock(item));
        return this;
    }


    private ConditionBlock allEqNormal(Map<String, String> map) {
        if (map == null || map.size() <= 0) {
            log.debug("allEq parameter map is null or the size is 0");
            return this;
        }
        map.forEach(this::eq);
        return this;
    }

    private ConditionBlock eqNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Maybe you want to use method isNull");
            isNull(column);
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.EQ, column, value));
        return this;
    }

    private ConditionBlock neNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Maybe you want to use method isNotNull");
            isNotNull(column);
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.NE, column, value));
        return this;
    }

    private ConditionBlock gtNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.GT, column, value));
        return this;
    }

    private ConditionBlock geNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.GE, column, value));
        return this;
    }

    private ConditionBlock ltNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.LT, column, value));
        return this;
    }

    private ConditionBlock leNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value == null) {
            log.warn("value not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.LE, column, value));
        return this;
    }

    private ConditionBlock betweenNormal(String column, String value1, String value2) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value1 == null || value2 == null) {
            log.warn("value1 and value2 in between not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.BETWEEN, column, value1, value2));
        return this;
    }

    private ConditionBlock notBetweenNormal(String column, String value1, String value2) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        if (value1 == null || value2 == null) {
            log.warn("value1 and value2 in between not be null, Discard this sql segment");
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.NOT_BETWEEN, column, value1, value2));
        return this;
    }

    private ConditionBlock likeNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.LIKE, column, value == null ? "NULL" : value));
        return this;
    }

    private ConditionBlock notLikeNormal(String column, String value) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.NOT_LIKE, column, value == null ? "NULL" : value));
        return this;
    }

    private ConditionBlock isNullNormal(String... column) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        for (String c : column) {
            if (c == null || "".equals(c)) {
                log.warn("in the method named isNull, the column name not be null or '', Discard this sql segment");
                return this;
            }
        }
        for (String c : column) {
            this.segs.add(new ConditionSegment(SegmentType.IS_NULL, c));
        }
        return this;
    }

    private ConditionBlock isNotNullNormal(String... column) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        for (String c : column) {
            if (c == null || "".equals(c)) {
                log.warn("in the method named isNull, the column name not be null or '', Discard this sql segment");
                return this;
            }
        }
        for (String c : column) {
            this.segs.add(new ConditionSegment(SegmentType.IS_NOT_NULL, c));
        }
        return this;
    }

    private ConditionBlock inNormal(String column, List<String> values) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.IN, values));
        return this;
    }

    private ConditionBlock notInNormal(String column, List<String> values) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.NOT_IN, values));
        return this;
    }

    private ConditionBlock applyNormal(String column, List<String> values) {
        if (findErrorColumnNames(column)) {
            return this;
        }
        this.segs.add(new ConditionSegment(SegmentType.APPLY, values));
        return this;
    }

    /**
     * 检查column字符串是否有错，null或空字符串，有错返回true
     */
    private boolean findErrorColumnNames(String... columns) {
        for (String column : columns) {
            if (column == null || column.length() <= 0) {
                log.warn("the column name is not be null or '', Discard this sql segment");
                return true;
            }
        }
        return false;
    }
}
